//
//  CommandExecutionController.m
//
//	Serial Remote for Mac - Control mac actions via serial commands
//	Copyright (C) 2010  Jeremiah McConnell
//
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#import "CommandExecutionController.h"
#import "BindingsData.h"
#import "SoundController.h"

#import <Growl/Growl.h>

static CommandExecutionController *CommandExecutionControllerSingleton = nil;

// Stored bindings data
NSMutableArray *bindings = nil;

// Key code transformer
NSValueTransformer *keyTransformer = nil;

// Specials dict
static NSDictionary *specialsDict = nil;

@implementation CommandExecutionController

@synthesize repeatCommand;
@synthesize prevCommand;

#pragma mark -
#pragma mark Class Methods

+ (CommandExecutionController *)sharedCommandExecutionController
{
	@synchronized(self) {
		if (CommandExecutionControllerSingleton == nil) {
			CommandExecutionControllerSingleton = [[self alloc] init];
		}
	}
	return CommandExecutionControllerSingleton;
}

#pragma mark -
#pragma mark Initialization Methods

+ (void)initialize
{
	specialsDict = [[NSDictionary alloc] initWithObjectsAndKeys:
					[NSNumber numberWithInt:122], @"F1", 
					[NSNumber numberWithInt:120], @"F2", 
					[NSNumber numberWithInt:99], @"F3", 
					[NSNumber numberWithInt:118], @"F4", 
					[NSNumber numberWithInt:96], @"F5", 
					[NSNumber numberWithInt:97], @"F6", 
					[NSNumber numberWithInt:98], @"F7", 
					[NSNumber numberWithInt:100], @"F8", 
					[NSNumber numberWithInt:101], @"F9", 
					[NSNumber numberWithInt:109], @"F10", 
					[NSNumber numberWithInt:103], @"F11", 
					[NSNumber numberWithInt:111], @"F12", 
					[NSNumber numberWithInt:49], @"Space", 
					[NSNumber numberWithInt:51], @"Delete Left", 
					[NSNumber numberWithInt:117], @"Delete Right", 
					[NSNumber numberWithInt:71], @"Clear",
					[NSNumber numberWithInt:123], @"Left Arrow",
					[NSNumber numberWithInt:124], @"Right Arrow",
					[NSNumber numberWithInt:126], @"Up Arrow",
					[NSNumber numberWithInt:125], @"Down Arrow",
					[NSNumber numberWithInt:53], @"Escape",
					[NSNumber numberWithInt:121], @"Page Down",
					[NSNumber numberWithInt:116], @"Page Up",
					[NSNumber numberWithInt:36], @"ReturnR2",
					[NSNumber numberWithInt:76], @"Return",
					[NSNumber numberWithInt:48], @"Tab",
					nil];
}

- (id)init
{
	if ((self = [super init])) {
		// Initialize shared bindings data
		bindings = [BindingsData sharedBindingsData];
		
		// Initialize the key code transformer
		keyTransformer = [NSValueTransformer valueTransformerForName:@"StringToKeyCodeTransformer"];
		
		// Bind repeat string to user defaults
		[self bind:@"repeatCommand" toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath:@"values.commandRepeatString" options:nil];
		
		// Initialize the Growl bridge
		[GrowlApplicationBridge setGrowlDelegate:nil];
	}
	
	return self;
}

#pragma mark -
#pragma mark Control Methods

- (void)sendKeyPress:(NSMutableDictionary*)item
{
	NSNumber *keyCode;
	CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
	NSString *keys = [item valueForKey:@"keyPressKey"];
	
	if ([specialsDict valueForKey:keys] != nil) {
		keyCode = [specialsDict valueForKey:keys];
	}
	else {
		unichar key = [keys characterAtIndex:0];
		NSString *keyStr = [[NSString stringWithCharacters:&key length:1] capitalizedString];
		keyCode = [keyTransformer reverseTransformedValue:keyStr];
	}

	// Handle Modifiers
	NSUInteger modifiers = 0;
	NSMutableString *modString = [NSMutableString stringWithCapacity:30];

	if ([[item objectForKey:@"keyPressModifierCommand"] boolValue]) {
		modifiers = modifiers | kCGEventFlagMaskCommand;
		[modString insertString:@"Cmd - " atIndex:0];
	}
	
	if ([[item objectForKey:@"keyPressModifierOption"] boolValue]) {
		modifiers = modifiers | kCGEventFlagMaskAlternate;
		[modString insertString:@"Alt - " atIndex:0];
	}

	if ([[item objectForKey:@"keyPressModifierControl"] boolValue]) {
		modifiers = modifiers | kCGEventFlagMaskControl;
		[modString insertString:@"Ctrl - " atIndex:0];
	}
	
	if ([[item objectForKey:@"keyPressModifierShift"] boolValue]) {
		modifiers = modifiers | kCGEventFlagMaskShift;
		[modString insertString:@"Shift - " atIndex:0];
	}

	CGEventRef eventRefDown = CGEventCreateKeyboardEvent(source, (CGKeyCode)[keyCode intValue], true);
	CGEventRef eventRefUp = CGEventCreateKeyboardEvent(source, (CGKeyCode)[keyCode intValue], false);
	
	CGEventSetFlags(eventRefDown, (CGEventFlags)modifiers);
	CGEventSetFlags(eventRefUp, (CGEventFlags)modifiers);
	
	CGEventPost(kCGHIDEventTap, eventRefDown);
	CGEventPost(kCGHIDEventTap, eventRefUp);
	
	CFRelease(eventRefDown);
	CFRelease(eventRefUp);

	CFRelease(source);
	
	// Send Growl notification
	NSString *desc = [modString stringByAppendingString:keys];
	if ([[NSUserDefaults standardUserDefaults] boolForKey:@"useGrowlNotifications"])
		[GrowlApplicationBridge notifyWithTitle:@"Key Press"
									description:desc
							   notificationName:@"Key Press"
									   iconData:nil
									   priority:0
									   isSticky:NO
								   clickContext:nil];
	
}

- (void)execute:(NSString*)command
{
	
	NSString *commandToDo;
	
	if ([command isEqualToString:repeatCommand]) {
		// use previous command code
		commandToDo = prevCommand;
	}
	else {
		commandToDo = command;
	}

	
	for (NSMutableDictionary *item in bindings) {
		NSString *itemCommand = [item valueForKey:@"serialCommand"];
	
		if ([itemCommand isEqualToString:commandToDo]) {
			// Execute this item
			NSString *type = [item valueForKey:@"type"];
			
			if ([type isEqualToString:@"Audio"]) {
				
				// This is an audo command
				NSString *audioCommand = [item valueForKey:@"audioCommand"];
				
				if ([audioCommand isEqualToString:@"Mute"]) {
					
					// Send command
					BOOL muted = [[SoundController sharedSoundController] toggleMute];
					[self setPrevCommand:nil];
					
					// Send Growl notification
					NSString *desc;
					if (muted) {
						desc = @"Volume is now muted";
					}
					else {
						desc = @"Volume is now unmuted";
					}
					
					if ([[NSUserDefaults standardUserDefaults] boolForKey:@"useGrowlNotifications"])
						[GrowlApplicationBridge notifyWithTitle:@"Mute"
													description:desc
											   notificationName:@"Volume"
													   iconData:nil
													   priority:0
													   isSticky:NO
												   clickContext:nil];
				}
				else if ([audioCommand isEqualToString:@"Volume Up"]) {
					
					// Send command
					NSUInteger volume = [[SoundController sharedSoundController] increaseVolume];
					[self setPrevCommand:commandToDo];
					
					// Send Growl notification
					NSString *desc = [NSString stringWithFormat:@"Volume set to %d%%", volume];
					if ([[NSUserDefaults standardUserDefaults] boolForKey:@"useGrowlNotifications"])
						[GrowlApplicationBridge notifyWithTitle:@"Volume Up"
													description:desc
											   notificationName:@"Volume"
													   iconData:nil
													   priority:0
													   isSticky:NO
												   clickContext:nil];
					
				}
				else if ([audioCommand isEqualToString:@"Volume Down"]) {
					
					// Send command
					NSUInteger volume = [[SoundController sharedSoundController] decreaseVolume];
					[self setPrevCommand:commandToDo];
					
					// Send Growl notification
					NSString *desc = [NSString stringWithFormat:@"Volume set to %d%%", volume];
					if ([[NSUserDefaults standardUserDefaults] boolForKey:@"useGrowlNotifications"])
						[GrowlApplicationBridge notifyWithTitle:@"Volume Down"
													description:desc
											   notificationName:@"Volume"
													   iconData:nil
													   priority:0
													   isSticky:NO
												   clickContext:nil];
					
				}
				
			}
			else if ([type isEqualToString:@"Key Press"]) {
				
				// This is a keypress event
				[self sendKeyPress:item];
				[self setPrevCommand:nil];
				
			}
			else if ([type isEqualToString:@"Script"]) {
				
				//BOOL shell = [[item objectForKey:@"executeWithShell"] boolValue];
				BOOL shell = YES;
				
				// Spawn a task and forget (cross fingers)
				NSString *cmd = [item valueForKey:@"scriptCommand"];
				NSString *path;
				NSString *argsStr;
				
				if (shell) {
					path = @"/bin/bash";
					argsStr = cmd;
				}
				
				[NSTask launchedTaskWithLaunchPath:path arguments:[NSArray arrayWithObjects:@"-c", argsStr, nil]];
				
				// Send Growl notification
				if ([[NSUserDefaults standardUserDefaults] boolForKey:@"useGrowlNotifications"])
					[GrowlApplicationBridge notifyWithTitle:@"Script Executed"
												description:argsStr
										   notificationName:@"Script"
												   iconData:nil
												   priority:0
												   isSticky:NO
											   clickContext:nil];
			}
		}
	}
}

@end
